package com.geqian.web.service.impl;

import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.geqian.common.common.PageRequest;
import com.geqian.common.common.PageResult;
import com.geqian.common.common.ResponseResult;
import com.geqian.web.entity.Menu;
import com.geqian.web.entity.RoleMenu;
import com.geqian.web.entity.Table;
import com.geqian.web.generator.FileMappingHandler;
import com.geqian.web.generator.TypeConverter;
import com.geqian.web.mapper.MenuMapper;
import com.geqian.web.mapper.RoleMenuMapper;
import com.geqian.web.mapper.TableMapper;
import com.geqian.web.pojo.ColumnDefinition;
import com.geqian.web.pojo.Index;
import com.geqian.web.service.TableService;
import com.geqian.web.utils.*;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.io.File;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
public class TableServiceImpl extends ServiceImpl<TableMapper, Table> implements TableService {


    private String tableSchema;

    @Resource
    private MenuMapper menuMapper;

    @Resource
    private RoleMenuMapper roleMenuMapper;

    @Resource
    private FileMappingHandler fileMappingHandler;


    @Value("${spring.datasource.url}")
    private void setTableSchema(String url) {
        tableSchema = StringCommonUtils.findMatchingString(url, "\\d+/(.+)\\?", 1);
    }


    @Override
    public ResponseResult<PageResult<Table>> getTableList(PageRequest pageRequest) {
        LambdaQueryWrapper<Table> wrapper = new LambdaQueryWrapper<>();
        String key = pageRequest.getKey();
        if (StringUtils.hasText(key)) {
            wrapper.like(Table::getTableName, key);
        }
        PageHelper.startPage(pageRequest.getPageNum(), pageRequest.getPageSize());
        List<Table> tableList = this.baseMapper.selectList(wrapper);
        PageInfo<Table> pageInfo = new PageInfo<>(tableList);
        PageResult<Table> pageResult = PageUtils.getPageResult(pageInfo);
        return ResponseResult.success(pageResult);
    }

    @Override
    public ResponseResult<String> updateTable(Table table, String token) {
        if (Objects.nonNull(table)) {
            String userId = JwtUtils.parseForValue(token, "userId", String.class);
            this.updateById(table);
        }
        return ResponseResult.success();
    }

    @Override
    public ResponseResult<String> addTable(Table table, String token) {
        if (Objects.nonNull(table)) {
            String userId = JwtUtils.parseForValue(token, "userId", String.class);
            this.save(table);
        }
        return ResponseResult.success();
    }

    @Override
    public ResponseResult<String> deleteTable(List<Table> tableList) {
        if (tableList != null && !tableList.isEmpty()) {
            boolean flag = tableList.stream().anyMatch(Table::getFlag);
            if (flag) {
                return ResponseResult.fail("请先卸载已生成的代码，再进行删除");
            }
            this.baseMapper.deleteBatch(tableList);
            return ResponseResult.success();
        }
        return ResponseResult.fail();
    }


    @Override
    public ResponseResult<List<String>> getTableNames() {
        List<String> tables = this.baseMapper.selectTables(tableSchema);
        return ResponseResult.success(tables);
    }


    @Transactional
    @Override
    public ResponseResult<String> importTable(String tableName) {
        List<Table> tables = this.baseMapper.selectTableByTableName(tableSchema, tableName);
        Table table = tables.get(0);
        String entityName = StringCommonUtils.underlineToBigHump(table.getTableName());
        table.setEntityName(entityName);
        String primaryKeys = tables.stream().map(Table::getPrimaryKey).collect(Collectors.joining("，"));
        table.setPrimaryKey(primaryKeys);
        this.baseMapper.insert(table);
        return ResponseResult.success();
    }


    @Transactional(rollbackFor = Exception.class)
    @Override
    public ResponseResult<String> generateCode(Table table, String token) {

        Long userId = Long.valueOf(JwtUtils.parseForValue(token, "userId", String.class));

        try {
            //校验实体类文件是否在路径下已存在，避免覆盖已存在文件
            verifyEntityFile(table.getEntityName());
        } catch (Exception e) {
            e.printStackTrace();
            return ResponseResult.fail(e.getMessage());
        }

        //构建 freemark 文件模板所需基本参数
        Map<String, Object> params = buildParameters(table);


        try {
            //筛选出最优的可以作为删除、更改key的字段（数量优先级大于键类型，主键优先级大于唯一键）
            addUniqueKeys(table, params);
        } catch (Exception e) {
            return ResponseResult.fail(e.getMessage());
        }

        //设置列、属性相关数据
        addFieldColumnMappings(table, params);

        //生成相关文件
        startGenerateFiles(params, table);

        //添加菜单
        addMenu(table, userId);

        return ResponseResult.success();
    }


    private Map<String, Object> buildParameters(Table table) {
        Map<String, Object> params = new HashMap<>();
        params.put("className", table.getEntityName());
        params.put("description", table.getTableComment());
        params.put("tableName", table.getTableName());
        params.put("author", "geqian");
        params.put("date", LocalDateTimeUtils.getNow());
        return params;
    }

    private void verifyEntityFile(String entityName) {
        String entityPath = fileMappingHandler.getEntityPath();
        File fileDir = new File(entityPath);
        File[] files = fileDir.listFiles();

        List<String> entitys = Arrays.stream(files).map(file -> {
            String fileName = file.getName().replace(".java", "");
            return fileName;
        }).collect(Collectors.toList());

        if (entitys.contains(entityName)) {
            throw new RuntimeException("当前实体名称已存在，请实体名称更改后再试");
        }
    }


    private void addMenu(Table table, Long userId) {
        //添加菜单
        String projectName = table.getEntityName();
        projectName = Character.toLowerCase(projectName.charAt(0)) + projectName.substring(1);
        Menu menu = new Menu();
        menu.setRouteName(projectName);
        menu.setLabel(table.getTableComment());
        menu.setParentId(9L);
        menu.setPath("/" + projectName);
        menu.setComponent("views/generator/" + table.getEntityName());
        menu.setMenuType(1);
        menu.setVisible("0");
        menu.setStatus("0");
        menu.setPermissions("generator:projects:" + projectName);
        menu.setIcon("el-icon-s-promotion");
        menu.setCreateBy(userId);
        menu.setOrderBy(0);
        menu.setCreateTime(new Date());
        menu.setUpdateBy(userId);
        menu.setUpdateTime(new Date());
        menu.setDelFlag(0);
        menu.setRemark(table.getTableName());
        this.menuMapper.insertReturnMenuId(menu);

        //系统管理员添加菜单权限
        RoleMenu roleMenu = new RoleMenu();
        roleMenu.setMenuId(menu.getMenuId());
        roleMenu.setRoleId(1L);
        this.roleMenuMapper.insert(roleMenu);

    }


    @Override
    public ResponseResult<String> uninstall(Table table) {
        if (table != null) {
            try {
                List<String> files = JSON.parseArray(table.getFiles(), String.class);
                for (String filePath : files) {
                    File file = new File(filePath);
                    if (file.exists()) {
                        file.delete();
                        log.info("成功删除文件：{}", filePath);
                    }
                }
                table.setFlag(false);
                this.baseMapper.updateById(table);

                LambdaQueryWrapper<Menu> menuWrapper = new LambdaQueryWrapper<>();
                menuWrapper.eq(Menu::getRouteName, table.getEntityName());
                Menu menu = this.menuMapper.selectOne(menuWrapper);
                this.menuMapper.delete(menuWrapper);

                LambdaQueryWrapper<RoleMenu> roleMenuWrapper = new LambdaQueryWrapper<>();
                roleMenuWrapper.eq(RoleMenu::getMenuId, menu.getMenuId());

                this.roleMenuMapper.delete(roleMenuWrapper);

                //this.baseMapper.clearTable(table.getTableName());

                return ResponseResult.success("卸载成功");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return ResponseResult.success("卸载失败");
    }

    /**
     * 添加属性列名映射相关数据
     *
     * @param table
     * @param params
     */
    private void addFieldColumnMappings(Table table, Map<String, Object> params) {
        List<ColumnDefinition> columnDefinitions = this.baseMapper.selectColumnDefinitions(table.getTableSchema(), table.getTableName());
        for (ColumnDefinition columnDefinition : columnDefinitions) {
            String fieldName = StringCommonUtils.underlineToSmallHump(columnDefinition.getColumnName());
            columnDefinition.setFieldName(fieldName);
            String columnType = columnDefinition.getColumnType();
            columnDefinition.setJdbcType(TypeConverter.getJdbcType(columnType));
            columnDefinition.setFieldType(TypeConverter.getJavaType(columnType));
        }
        params.put("dataList", columnDefinitions);
    }


    private void addUniqueKeys(Table table, Map<String, Object> params) {

        List<Index> indexList = this.baseMapper.selectIndexList(tableSchema, table.getTableName());

        if (!CollectionUtils.isEmpty(indexList)) {

            indexList = indexList.stream()
                    .collect(Collectors.groupingBy(index -> new AbstractMap.SimpleEntry<>(index.getIndexName(), index.getIndexType())))
                    .values()
                    .stream()
                    .min((before, next) -> {
                        int bs = before.size();
                        int ns = next.size();
                        if (before.size() != next.size()) {
                            return bs - ns;
                        } else {
                            int bw = bs + before.get(0).getIndexType();
                            int nw = ns + next.get(0).getIndexType();
                            return bw - nw;
                        }
                    })
                    .get();

            List<AbstractMap.SimpleEntry<String, String>> keySet = indexList.stream().map(index -> new AbstractMap.SimpleEntry<>(index.getColumnName(), StringCommonUtils.underlineToSmallHump(index.getColumnName()))).collect(Collectors.toList());

            params.put("keySet", keySet);


        } else {
            throw new RuntimeException("没有可用的删除字段，请确保含有一个主键或唯一索引");
        }
    }


    /**
     * 开始生成文件
     *
     * @param params
     */
    private void startGenerateFiles(Map<String, Object> params, Table table) {

        String entityName = table.getEntityName();

        Map<String, String> fileRelationshipMapping = fileMappingHandler.getFileRelationshipMapping(entityName);

        try {
            for (Map.Entry<String, String> entry : fileRelationshipMapping.entrySet()) {
                String templateFile = entry.getKey();
                String outputFile = entry.getValue();
                TemplateUtils.fillTemplate(params, templateFile, outputFile);
                log.info("成功生成文件：{}", entry.getValue());
            }
            Collection<String> outputFiles = fileRelationshipMapping.values();
            table.setFlag(true);
            table.setFiles(JSON.toJSONString(outputFiles));
            this.updateById(table);
        } catch (Exception e) {
            for (String outputFile : fileRelationshipMapping.values()) {
                File file = new File(outputFile);
                if (file.exists()) {
                    file.delete();
                }
            }
            throw new RuntimeException("代码生成失败", e);
        }

    }

}




